package top.lingkang.finalsecurity.solonplugin.config;

import org.noear.solon.core.handle.Context;
import org.noear.solon.core.handle.Filter;
import org.noear.solon.core.handle.FilterChain;
import top.lingkang.finalsecurity.common.base.FinalAuth;
import top.lingkang.finalsecurity.common.base.FinalHttpProperties;
import top.lingkang.finalsecurity.common.error.FinalBaseException;
import top.lingkang.finalsecurity.common.error.FinalNotLoginException;
import top.lingkang.finalsecurity.common.error.FinalPermissionException;
import top.lingkang.finalsecurity.common.utils.AuthUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 应该只初始化一次此配置：
 * <pre>
 * {@code
 * @Component
 * public class MyFinalConfig extends FinalSecurityConfiguration {
 *
 *     @Override
 *     protected void config(FinalHttpProperties properties) {
 *         properties.checkAuthorize()
 *                 .pathMatchers("/user").hasAnyRole("user", "vip1") // 有其中任意角色就能访问
 *                 .pathMatchers("/vip/**").hasAllRole("user", "vip1") // 必须有所有角色才能访问
 *                 .pathMatchers("/**").hasLogin();// 需要登录才能访问
 *
 *         // 排除鉴权路径匹配, 匹配优先级别：排除路径 > checkAuthorize > 注解
 *         properties.setExcludePath("/login", "/logout", "/user/login/app", "/**.js", "/**.css");
 *     }
 * }
 * }
 * </pre>
 *
 * @author lingkang
 * Created by 2022/10/28
 * @since 3.0.0
 */
public class FinalSecurityConfiguration implements Filter {
    public FinalHttpProperties properties;

    @Override
    public void doFilter(Context ctx, FilterChain chain) throws Throwable {
        try {
            String path = ctx.path();
            // 缓存相关
            if (properties.getCheckPathCache().getExcludePath().contains(path)) {
                chain.doFilter(ctx);
                return;
            } else if (properties.getCheckPathCache().getAuths().containsKey(path)) {
                FinalAuth[] finalAuths = properties.getCheckPathCache().getAuths().get(path);
                for (FinalAuth auth : finalAuths) {
                    auth.check(new FinalSessionObjectSolon(ctx));
                }

                chain.doFilter(ctx);
                return;
            }

            // 排除
            for (String url : properties.getExcludePath()) {
                if (AuthUtils.matcher(url, path)) {
                    properties.getCheckPathCache().getExcludePath().add(path);// 添加缓存
                    chain.doFilter(ctx);
                    return;
                }
            }

            // 检查角色
            HashMap<String, FinalAuth> checkAuth = properties.getCheckAuthorize().getAuthorize();
            List<FinalAuth> auths = new ArrayList<>();
            for (Map.Entry<String, FinalAuth> entry : checkAuth.entrySet()) {
                if (AuthUtils.matcher(entry.getKey(), path)) {
                    auths.add(entry.getValue());
                }
            }

            // cache
            properties.getCheckPathCache().getAuths().put(path, AuthUtils.AllToOne(auths.toArray(new FinalAuth[auths.size()])));

            // 不需要校验
            if (auths.isEmpty()) {
                chain.doFilter(ctx);
                return;
            }

            // 执行检查
            for (FinalAuth auth : auths) {
                auth.check(new FinalSessionObjectSolon(ctx));
            }

            //放行
            chain.doFilter(ctx);
        } catch (Exception e) {
            if (FinalBaseException.class.isAssignableFrom(e.getClass())) {
                if (e instanceof FinalPermissionException) {
                    properties.getExceptionHandler().permissionException(e, ctx, ctx);
                } else if (e.getCause() instanceof FinalPermissionException)
                    properties.getExceptionHandler().permissionException(e.getCause(), ctx, ctx);
                else if (e instanceof FinalNotLoginException) {
                    properties.getExceptionHandler().notLoginException(e, ctx, ctx);
                } else if (e.getCause() instanceof FinalNotLoginException)
                    properties.getExceptionHandler().notLoginException(e.getCause(), ctx, ctx);
                else
                    properties.getExceptionHandler().exception(e, ctx, ctx);
                ctx.setHandled(true);
            } else {
                throw e;
            }
        }
    }

    protected void config(FinalHttpProperties config) {
        System.out.println("final-security use default config");
    }

    public void init() {
        properties = new FinalHttpProperties();
        properties.setExceptionHandler(new DefaultFinalExceptionHandler());
        config(properties);
    }

    public FinalHttpProperties getProperties() {
        return properties;
    }

    /**
     * 清理缓存
     */
    public void clearCache() {
        properties.getCheckPathCache().getExcludePath().clear();
        properties.getCheckPathCache().getAuths().clear();
    }
}
